'''
目标网站:https://www.dangdang.com/
要求:
    利用(xpath，bs4，自动化爬虫drissionpage)(任意)，爬取菜谱下，
    1.8个类别下的每个品类的10页商品数据('品类，商品名称，商品价格，年份，出版社，评论条数")
    2.保存为 当当网菜谱数据.x1sx文件

'''
# 第一部分：使用drissionpage自动化模拟进行数据采集
from DrissionPage._pages.chromium_page import ChromiumPage
import time
# 保存excel数据
import pandas as pd
import openpyxl
# 第二部分：使用Selenium-chrome(有头)和Selenium-phantomjs(无头)自动化模拟进行数据采集
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
# 保存json数据
import json
import sys
# 保存mysql数据
import pymysql
# 导入画图模块
import matplotlib.pyplot as plt
# 导入设置弹窗显示图形的功能
import matplotlib
from pyecharts import charts as cht


class JDDrissionPageSpider:
    def __init__(self, target_url):
        print("======================================================")
        print("======！！！！欢迎来到自动化DrissionPage界面！！！！=======")
        print("====！！！！即将开始实现网页有头窗口数据抓取开始！！！！======")
        print("======================================================")
        # 数据保存
        # 每一页的数据
        self.all_page_data = {}
        # 所有数据
        self.all_menu_category = []
        self.all_menu_name = []
        self.all_menu_price = []
        self.all_menu_years_date = []
        self.all_menu_published = []
        self.all_menu_rating = []
        self.all_menu_url = []

        # 创建浏览器对象
        self.driver = ChromiumPage()
        # 访问目标网站
        self.driver.get(target_url)
        # 定位输入框--通过ID(#)进行定位
        self.driver.ele('''#key_S''').input("菜谱")
        # 点击检索，进行跳转到 菜谱 信息页面--通过类(.)
        self.driver.ele('''.button''').click()
        # 解析数据
        time.sleep(1)
        # 获取八大类型的菜谱类型
        for index in range(1, 9):
            xpath_category = f'''xpath://div[@id="50447"]/span[{index}]/a'''
            menu_category = self.driver.ele(xpath_category).text
            self.driver.ele(xpath_category).click()
            print("获取菜谱的类型是：", menu_category, f"--{index}--")

            # 控制数据翻页页数
            for page in range(1, 11):
                self.parse_data(page, menu_category)
            # 这个进行点击算新页面，目的是显示菜谱八大品类
            self.driver.ele('''xpath://a[@title="家常菜"] | //a[@title="菜谱"] | //a[text()="家常菜谱"]''').click()
        # 调用自定义的保存方法
        self.save_excel_data(self.all_page_data)

    def parse_data(self, page, menu_category):
        # 解析每页的数据
        time.sleep(1)
        menu_data_list = self.driver.eles('''xpath: //ul[@class="bigimg"]/li''')
        print(f"本页--{page}--共有--{len(menu_data_list)}--个菜谱")
        for index, each in enumerate(menu_data_list, 1):
            print(f"这个是本页得第--{index}--个菜谱")
            # 品类
            menu_category = menu_category
            # 商品名称
            menu_name = each.ele('''xpath:.//p[@name="title"]/a''').attr('title')
            # 商品价格
            menu_price = each.ele('''xpath:.//span[@class="search_now_price"]''').text
            # 年份
            menu_years_date = each.ele('''xpath:.//p[@class="search_book_author"]/span[2]''').text.replace('/', '')
            # 出版社
            menu_published_obj = each.ele('''xpath:.//a[@name="P_cbs"]''')
            if menu_published_obj:
                menu_published = menu_published_obj.attr('title')
            else:
                menu_published = ""
            # # 评论条数--这个要对详情页发送请求获取数据，主要是有些没有就不显示
            menu_rating_obj = each.ele('''.search_comment_num''')
            if menu_rating_obj:
                menu_rating = menu_rating_obj.text
            else:
                menu_rating = "0条评论"
            # 详情链接
            menu_url = each.ele('''xpath:.//p[@name="title"]/a''').attr('href')
            print("品类：", menu_category)
            print("商品名称：", menu_name)
            print("商品价格：", menu_price)
            print("商品出版社：", menu_published)
            print("商品年份：", menu_years_date)
            print("评论条数：", menu_rating)
            print("商品详情链接：", menu_url)
            # 品类，商品名称，商品价格，年份，出版社，评论条数
            self.all_menu_category.append(menu_category)
            self.all_menu_name.append(menu_name)
            self.all_menu_price.append(menu_price)
            self.all_menu_published.append(menu_published)
            self.all_menu_years_date.append(menu_years_date)
            self.all_menu_rating.append(menu_rating)
            self.all_menu_url.append(menu_url)
        # 对每个菜谱类型数据进行翻页
        self.driver.ele('''xpath://a[@title="下一页"]''').click()

    def save_excel_data(self, data):
        # 用列表来存储所有数据--品类，商品名称，商品价格，年份，出版社，评论条数
        self.all_page_data["品类"] = self.all_menu_category
        self.all_page_data["商品名称"] = self.all_menu_name
        self.all_page_data["商品价格"] = self.all_menu_price
        self.all_page_data["年份"] = self.all_menu_years_date
        self.all_page_data["出版社"] = self.all_menu_published
        self.all_page_data["评论条数"] = self.all_menu_rating
        self.all_page_data["详情链接"] = self.all_menu_url
        # 创建pandas的DataFrame()对象df
        df = pd.DataFrame(self.all_page_data)
        # 调用pandas的to_excel保存数据
        df.to_excel('京东菜谱数据采集.xlsx')

class JDSeleniumChromeSpider:
    def __init__(self, target_url):
        print("======================================================")
        print("=====！！！！欢迎来到自动化Selenium-Chrome界面！！！！=====")
        print("====！！！！即将开始实现网页有头窗口数据抓取开始！！！！=====")
        print("======================================================")
        self.wb = openpyxl.Workbook()
        self.ws = self.wb.active
        # 添加头部数据
        # 品类，商品名称，商品价格，年份，出版社，评论条数
        self.ws.append(["品类", "商品名称", "商品价格", "年份", "出版社", "评论条数", "详情链接"])

        # 保存数据列表
        self.all_data_list = []
        self.every_category_dict = {}

        # 1.创建驱动对象
        self.driver = webdriver.Chrome()
        # 2.访问目标网站
        self.driver.get(target_url)
        # 3.定位输入框--通过ID
        self.driver.find_element(By.ID, 'key_S').send_keys("菜谱")
        # 4.点击搜索--通过class
        self.driver.find_element(By.CLASS_NAME, 'button').send_keys(Keys.ENTER)
        # 获取八大菜谱类型
        for i in range(1, 9):
            # 奖项同一类的数据存储到一起
            self.all_every_category_list = []
            self.all_every_category_dict = {}
            xpath_yufa = f'//div[@id="50447"]/span[{i}]/a'
            menu_category = self.driver.find_element(By.XPATH, xpath_yufa).get_attribute("title")
            # 点击菜谱类型
            self.driver.find_element(By.XPATH, xpath_yufa).send_keys(Keys.ENTER)
            for page in range(1, 11):
                self.parse_data(page, menu_category)
            # 刷新页面
            self.driver.find_element(By.XPATH, '//dd/a[@title="家常菜"] | //dd/a[@title="菜谱"] | //div[@class="crumbs_fb_left"]//a[text()="家常菜谱"]').send_keys(Keys.ENTER)
            # 将一类的数据信息保存到字典中
            self.every_category_dict[menu_category] = self.all_every_category_list
        # 将所有数据保存到列表中
        self.all_data_list.append(self.every_category_dict)
        self.save_JSON_data(self.all_data_list)

    def parse_data(self, page, menu_category):
        menu_data_list = self.driver.find_elements(By.XPATH, '//ul[@class="bigimg"]/li')
        print(f"当前--{page}--页有--{len(menu_data_list)}--个菜谱数据")
        for index, each in enumerate(menu_data_list, 1):

            print(f"这个是本页的第--{index}--个菜谱")
            # 品类
            menu_category = menu_category
            print("品类：", menu_category)
            # 商品名称
            menu_name = each.find_element(By.XPATH, './/p[@name="title"]/a').get_attribute("title")
            print("商品名称：", menu_name)
            # 商品价格
            menu_price = each.find_element(By.XPATH, './/span[@class="search_now_price"]').text
            print("商品价格：", menu_price)
            # 商品出版社
            if each.find_elements(By.XPATH, './/a[@name="P_cbs"]'):
                menu_published = each.find_element(By.XPATH, './/a[@name="P_cbs"]').get_attribute("title")
            else:
                menu_published = ""
            print("商品出版社：", menu_published)
            # 商品年份
            menu_years_date = each.find_element(By.XPATH, './/p[@class="search_book_author"]/span[2]').text.replace('/', '')
            print("商品年份：", menu_years_date)
            # 评论条数--
            if each.find_elements(By.XPATH, './/a[@class="search_comment_num"]'):
                menu_rating = each.find_element(By.XPATH, './/a[@class="search_comment_num"]').text
            else:
                menu_rating = ""
            print("评论条数：", menu_rating)
            # 商品详情链接
            menu_url = each.find_element(By.XPATH, './/p[@name="title"]/a').get_attribute("href")
            print("商品详情链接：", menu_url)
            self.all_every_category_dict['品类'] = menu_category
            self.all_every_category_dict['商品名称'] = menu_name
            self.all_every_category_dict['商品价格'] = menu_price
            self.all_every_category_dict['商品出版社'] = menu_published
            self.all_every_category_dict['商品年份'] = menu_years_date
            self.all_every_category_dict['评论条数'] = menu_rating
            self.all_every_category_dict['商品详情链接'] = menu_url
            self.all_every_category_list.append(self.all_every_category_dict)
            # 按照一列一列的数据保存
            self.save_openpyxl_excel_data(menu_category, menu_name, menu_price, menu_published, menu_years_date, menu_rating, menu_url)
        # 对该类菜谱的数据进行翻页
        # time.sleep(1)
        self.driver.find_element(By.XPATH, '//li[@class="next"]/a').send_keys(Keys.ENTER)

    def save_JSON_data(self, data):
        with open('京东数据抓取.json', 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)

    def save_openpyxl_excel_data(self, menu_category, menu_name, menu_price, menu_published, menu_years_date, menu_rating, menu_url):
        self.ws.append([menu_category, menu_name, menu_price, menu_published, menu_years_date, menu_rating, menu_url])
        self.wb.save("京东菜谱数据抓取.xlsx")

class JDSeleniumPhantomJSSpider:
    def __init__(self, target_url):
        print("======================================================")
        print("=======！！！！欢迎来到自动化PhantomJS界面！！！！========")
        print("====！！！！即将开始实现网页无头窗口数据抓取开始！！！！=====")
        print("======================================================")
        # 第一部分：数据库连接
        # 创建mysql数据库链接的对象
        self.con = pymysql.connect(
            host='127.0.0.1',
            port=3306,
            user='root',
            password='L11223344',
            database='jd_db',
            charset='utf8'
        )
        # 创建操作游标对象
        self.cur = self.con.cursor()
        # 第二部分：自动化实现
        # 常见浏览器对象
        self.driver = webdriver.PhantomJS()
        # 访问目标网站
        self.driver.get(target_url)
        # 定位输入框--ID
        self.driver.find_element(By.ID, 'key_S').send_keys("菜谱")
        # 定位搜索框--CLASS
        self.driver.find_element(By.CLASS_NAME, 'button').send_keys(Keys.ENTER)
        for i in range(1, 3):
            xpath_yufa = f'//div[@id="50447"]/span[{i}]/a'
            menu_category = self.driver.find_element(By.XPATH, xpath_yufa).get_attribute("title")
            self.driver.find_element(By.XPATH, xpath_yufa).send_keys(Keys.ENTER)
            for page in range(1, 3):
                self.parse_data(page, menu_category)
            # 刷新页面
            self.driver.find_element(By.XPATH, '//dd/a[@title="家常菜"] | //dd/a[@title="菜谱"] | //div[@class="crumbs_fb_left"]//a[text()="家常菜谱"]').send_keys(Keys.ENTER)


    def parse_data(self, page, menu_category):
        menu_data_list = self.driver.find_elements(By.XPATH, '//ul[@class="bigimg"]/li')
        print(f"当前--{page}--页有--{len(menu_data_list)}--个菜谱")
        for index, each in enumerate(menu_data_list, 1):
            every_data_list = []
            print(f"这个是本页的第--{index}--菜谱数据")
            # 品类
            menu_category = menu_category
            print("品类：", menu_category)
            # 商品名称
            menu_name = each.find_element(By.XPATH, './/p[@name="title"]/a').get_attribute("title")
            print("商品名称：", menu_name)
            # 商品价格
            menu_price = each.find_element(By.XPATH, './/span[@class="search_now_price"]').text
            print("商品价格：", menu_price)
            # 商品出版社
            if each.find_elements(By.XPATH, './/a[@name="P_cbs"]'):
                menu_published = each.find_element(By.XPATH, './/a[@name="P_cbs"]').get_attribute("title")
            else:
                menu_published = ""
            print("商品出版社：", menu_published)
            # 商品年份
            menu_years_date = each.find_element(By.XPATH, './/p[@class="search_book_author"]/span[2]').text.replace('/', '')
            print("商品年份：", menu_years_date)
            # 评论条数--
            if each.find_elements(By.XPATH, './/a[@class="search_comment_num"]'):
                menu_rating = each.find_element(By.XPATH, './/a[@class="search_comment_num"]').text
            else:
                menu_rating = ""
            print("评论条数：", menu_rating)
            # 商品详情链接
            menu_url = each.find_element(By.XPATH, './/p[@name="title"]/a').get_attribute("href")
            print("商品详情链接：", menu_url)
            every_data_list.append(menu_category)
            every_data_list.append(menu_name)
            every_data_list.append(menu_price)
            every_data_list.append(menu_published)
            every_data_list.append(menu_years_date)
            every_data_list.append(menu_rating)
            every_data_list.append(menu_url)
            # 将数据保存到数据库
            self.save_SQL_data(every_data_list)
        # 翻页
        time.sleep(1)
        self.driver.find_element(By.XPATH, '//li[@class="next"]/a').send_keys(Keys.ENTER)

    def save_SQL_data(self, data_list):
        sql = "insert into jd_tb(menu_category, menu_name, menu_price, menu_published, menu_years_date, menu_rating, menu_url) values(%s, %s, %s, %s, %s, %s, %s)"
        self.cur.execute(sql, data_list)
        self.con.commit()

class HandleData:
    def __init__(self):
        # 设置显示所有列
        pd.set_option('display.max_columns', None)
        # 注意这里由于使用openpyxl模块进行保存所以需要使用引擎设置为openpyxl
        self.df = pd.read_excel('京东菜谱数据抓取.xlsx', engine='openpyxl')
        # 计算价格平均值
        self.handle_everage_price_data()
        # 每类别评论总数柱状图
        self.handle_sum_comment_data()
        # 每年出版书籍数量折线图
        self.handle_every_year_book_data()
        # 计算每个类型的出版社数据量
        self.handle_every_book_num()

    # 第一部分：
    # 1.计算每类别平均价格
    def handle_everage_price_data(self):
        # 1.# 删除价格为空的数据，在转换数据类型--清洗多余的符号 ￥--使用分割即可
        self.df["商品价格"] = self.df["商品价格"].apply(lambda x: str(x).split('¥')[1].split('起')[0].split(' ')[0] if '¥' in str(x) else 0).astype('float')
        # print(self.df["商品价格"])
        # 2.对菜谱 品类 进行分组，并对 价格 求平均值
        self.df1 = self.df.groupby("品类").agg(
            {
                "商品价格":"mean"
            }
        ).rename(columns={"商品价格":"每类别平均价格"})

    # 2.计算每类别评论总数
    def handle_sum_comment_data(self):
        # 1.对评论人数数据进行清洗，并将数据转化为整数类型
        self.df["评论条数"] = self.df["评论条数"].apply(lambda x: str(x).split("条评论")[0] if "条评论" in str(x) else 0).astype("int64")
        # 2.先对 品类 进行分组，再对 评论求 总和
        self.df2 = self.df.groupby("品类").agg(
            {
                "评论条数":"sum"
            }
        ).rename(columns={"评论条数":"每类别评论总数"})

    # 3.计算每年出版书籍数量
    def handle_every_year_book_data(self):
        # 1.对 年份 进行数据清理，去除空值，按行
        self.df = self.df.dropna(subset=["年份"], axis=0)
        # 2.将 年份 时间转化为Date日期类型
        self.df['年份'] = pd.to_datetime(self.df["年份"]).dt.year
        print(self.df['年份'])
        # 3.对年份进行分组，再进行 统计数量
        self.df3 = self.df.groupby("年份").agg(
            {
                "出版社":"count"
            }
        ).rename(columns={
            "出版社":"每年出版书籍数量"
        })

    # 4.计算每个类型的出版社数据量
    def handle_every_book_num(self):
        # 1.对 出版社 进行数据清洗，去掉空值， 按行
        self.df = self.df.dropna(subset=["出版社"], axis=0)
        # 2.对 出版社 进行分组，并且统计 商品的数量，同时需要进行降序排序，之后获取前五行数据
        # 注意这里df.sort_values(by="列名") 默认是升序即 ascending=True  降序需要写成 asecnding=False
        self.df4 = self.df.groupby("出版社").agg(
            {
                "商品名称":"count"
            }
        ).rename(columns={"商品名称":"每个类型的出版社数据量"}).sort_values(by="每个类型的出版社数据量", ascending=False).head()

    # 第二部分
    # 1.绘制每类别平均价格柱状图
    def handle_draw_price_bar(self):
        x = self.df1.index.values.tolist()
        y = self.df1["每类别平均价格"].values.tolist()
        plt.xlabel("类别")
        plt.ylabel("每类别平均价格")
        plt.title("每类别平均价格柱状图")
        plt.bar(x, y)

    # 2.绘制每类别评论总数柱状图
    def handle_draw_comment_bar(self):
        x = self.df2.index.values.tolist()
        y = self.df2["每类别评论总数"].values.tolist()
        plt.xlabel("品类")
        plt.ylabel("每类别评论总数")
        plt.title("每类别评论总数柱状图")
        plt.bar(x, y)

    # 3.绘制每年出版书籍数量折线图
    def handle_draw_book_plot(self):
        x = self.df3.index.values.tolist()
        y = self.df3["每年出版书籍数量"].values.tolist()
        plt.xlabel("年份")
        plt.ylabel("书版的数量")
        plt.title("每年出版书籍数量折线图")
        plt.plot(x, y)

    # 4.绘制价格与评论数量关系散点图
    def handle_draw_price_and_comment_scatter(self):
        x = self.df["商品价格"].values.tolist()
        y = self.df["评论条数"].values.tolist()
        plt.xlabel("商品价格")
        plt.ylabel("评论条数")
        plt.title("价格与评论数量关系散点图")
        plt.scatter(x, y)

    # 5.绘制前5名出版社书籍数量柱状图
    def handle_draw_head_5_book_bar(self):
        x = self.df4.index.values.tolist()
        y = self.df4["每个类型的出版社数据量"].values.tolist()
        plt.xlabel("出版社")
        plt.ylabel("出版社书籍数量")
        plt.title("前5名出版社书籍数量柱状图")
        plt.bar(x, y)

    def handle_draw(self):
        # 设置后端为 TkAgg
        matplotlib.use('TKAgg')
        # 显示负号
        plt.rcParams['axes.unicode_minus'] = False
        # 设置中文警告
        plt.rcParams['font.family'] = 'SimHei'
        # 设置画布大小
        plt.figure(figsize=(10, 5))
        print("=========！！！！欢迎来到matplotlib可视化界面！！！！=============")
        print("======================================================")
        print("1-每类别平均价格柱状图, \n2-每类别评论总数柱状图, \n3-每年出版书籍数量折线图, \n4-价格与评论数量关系散点图, \n5-前5名出版社书籍数量柱状图:")
        print("======================================================")
        print("=========！！！！请你选择你要进行操作！！！！==============")
        while True:
            choice_draw_option = input("你选择的操作是：")
            if choice_draw_option == '1':
                self.handle_draw_price_bar()
            elif choice_draw_option == '2':
                self.handle_draw_comment_bar()
            elif choice_draw_option == '3':
                self.handle_draw_book_plot()
            elif choice_draw_option == '4':
                self.handle_draw_price_and_comment_scatter()
            elif choice_draw_option == '5':
                self.handle_draw_head_5_book_bar()
            else:
                print("你要进行的绘图操作不在范围之内。。")
                sys.exit()
            plt.show()


if __name__ == '__main__':
    target_url = "https://www.dangdang.com/"
    print("======================================================")
    print("=========！！！！欢迎来到自动化爬虫界面！！！！=============")
    print("======================================================")
    print("0-退出，\n1-DrissionPage,\n2-SeleniumChrome, \n3-SeleniumPhantomJS, \n4-数据处理和可视化：")
    print("======================================================")
    print("=========！！！！请你选择你要进行操作！！！！==============")
    print("======================================================")
    choice_option = input("你选择的操作是：")

    if choice_option == '0':
        sys.exit()
    elif choice_option == '1':
        drissionPage = JDDrissionPageSpider(target_url)
    elif choice_option == '2':
        seleniumChrome = JDSeleniumChromeSpider(target_url)
    elif choice_option == '3':
        seleniumPhantomJS = JDSeleniumPhantomJSSpider(target_url)
    elif choice_option == '4':
        handleData = HandleData()
        # 绘图
        handleData.handle_draw()
    else:
        print("你输入的操作不在范围之内")
